package com.tianji.promotion.service.impl;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.domain.dto.PageDTO;
import com.tianji.common.exceptions.BadRequestException;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.CollUtils;
import com.tianji.common.utils.NumberUtils;
import com.tianji.common.utils.StringUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.promotion.constants.RedisConstant;
import com.tianji.promotion.domain.dto.CouponDiscountDTO;
import com.tianji.promotion.domain.dto.CouponMsgDto;
import com.tianji.promotion.domain.dto.OrderCouponDTO;
import com.tianji.promotion.domain.dto.OrderCourseDTO;
import com.tianji.promotion.domain.po.Coupon;
import com.tianji.promotion.domain.po.CouponScope;
import com.tianji.promotion.domain.po.ExchangeCode;
import com.tianji.promotion.domain.po.UserCoupon;
import com.tianji.promotion.domain.query.CouponQuery;
import com.tianji.promotion.domain.vo.CouponVO;
import com.tianji.promotion.domain.vo.OrderCouponDetail;
import com.tianji.promotion.enums.ExchangeCodeStatus;
import com.tianji.promotion.enums.UserCouponStatus;
import com.tianji.promotion.mapper.CouponMapper;
import com.tianji.promotion.mapper.UserCouponMapper;
import com.tianji.promotion.service.ICouponScopeService;
import com.tianji.promotion.service.IExchangeCodeService;
import com.tianji.promotion.service.IUserCouponService;
import com.tianji.promotion.utils.CodeUtil;
import com.tianji.promotion.utils.PermuteMyUtil;
import com.tianji.promotion.utils.discount.Discount;
import com.tianji.promotion.utils.discount.DiscountStrategy;
import jodd.util.CollectionUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Var;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * <p>
 * 用户领取优惠券的记录，是真正使用的优惠券信息 服务实现类
 * </p>
 *
 * @author yb0os1
 * @since 2025-03-09
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class UserCouponAsyncLuaServiceImpl extends ServiceImpl<UserCouponMapper, UserCoupon> implements IUserCouponService {
    private final CouponMapper couponMapper;
    private final IExchangeCodeService exchangeCodeService;
    private final StringRedisTemplate redisTemplate;
    private final RabbitMqHelper rabbitMqHelper;
    private final UserCouponMapper userCouponMapper;
    private final ICouponScopeService couponScopeService;
    private final Executor calculateOfferThreadPool;
    private final static RedisScript<Long> RECEIVE_COUPON;
    private final static RedisScript<String> COUPON_EXCHANGE_CODE;
    static{
        RECEIVE_COUPON = RedisScript.of(new ClassPathResource("lua/receiveCoupon.lua"), Long.class);
        COUPON_EXCHANGE_CODE=RedisScript.of(new ClassPathResource("lua/codeExchangeCoupon.lua"),String.class);
    }
    /**
     * 用户领取优惠券
     *
     * @param id 优惠券的di
     *           注意事项：
     *           校验信息，比如优惠券是否还有库存、优惠券的领取日期是否开始或者截止、当前用户是否已经领了最大限制的数量
     */
    @Override
    //使用lua脚本不加锁也是安全的
//    @MyLock(name = "lock:coupon：#{id}")
    public void receiveCoupon(Long id) {
        //2、从优惠券缓存中根据key查询优惠券
        String key1 = RedisConstant.CACHE_COUPON + id;
        Long userId = UserContext.getUser();
        String key2 = RedisConstant.CACHE_COUPON_USER_COUNT + id;
        Long res = redisTemplate.execute(RECEIVE_COUPON,
                List.of(key1, key2),
                userId.toString());
        if (NumberUtils.null2Zero(res).intValue()!=0){
            throw new BadRequestException("业务错误");
        }

        //6、发送信息 更新coupon和user_coupon表
        CouponMsgDto msg = CouponMsgDto.builder().couponId(id).userId(userId).build();
        rabbitMqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE_KEY, msg);
    }

    /**
     * 兑换码兑换优惠券
     *
     * @param code 兑换码
     *             <p>
     *             注意 需要校验兑换码和优惠券的信息
     *             兑换码格式
     *             兑换码是否已经兑换
     *             兑换码是否存在
     *             兑换码是否过期
     *             每个人限领数量
     */
    @Override
//    @MyLock(name = "lock:coupon：#{T(com.tianji.common.utils.UserContext).getUser()}")
    public void codeExchangeCoupon(String code) {
        //1、兑换码是否为空
        if (StringUtils.isBlank(code)) {
            throw new BadRequestException("兑换码不能为空");
        }
        //2、检验兑换码的格式并且解析兑换码  解析出来就是 自增id
        long serialNum = CodeUtil.parseCode(code);
        Long userId = UserContext.getUser();
        String res = redisTemplate.execute(COUPON_EXCHANGE_CODE,
                List.of(RedisConstant.COUPON_CODE_EXCHANGE_MARK,
                        RedisConstant.COUPON_CODE_MAX_ID,
                        RedisConstant.CACHE_COUPON,
                        RedisConstant.CACHE_COUPON_USER_COUNT),
                String.valueOf(serialNum),String.valueOf(serialNum+1000),
                userId.toString());
        long couponId = NumberUtils.parseLong(res);
        System.out.println(couponId);
        if (couponId<10){
            throw new BadRequestException("业务错误");
        }
        // 6.发送MQ消息通知
            CouponMsgDto msgDto = CouponMsgDto.builder().userId(userId).couponId(couponId).serialNum((int) serialNum).build();
            rabbitMqHelper.send(MqConstants.Exchange.PROMOTION_EXCHANGE, MqConstants.Key.COUPON_RECEIVE_KEY, msgDto);

    }


    /**
     * 查询我的优惠券
     * @param query 分页参数 状态 名字等
     * @return
     */
    @Override
    public PageDTO<CouponVO> queryMyCoupons(CouponQuery query) {
        //1、校验传入的参数
        if (query==null){
            throw new BizIllegalException("参数不能为空");
        }
        //2、根据传入的条件分页查询 主要仅仅是获得优惠券的id
        Page<UserCoupon> page = lambdaQuery()
                .eq(query.getStatus() != null, UserCoupon::getStatus, query.getStatus())
                .page(query.toMpPageDefaultSortByCreateTimeDesc());
        List<UserCoupon> records = page.getRecords();
        if (CollUtils.isEmpty(records)){
            return PageDTO.empty(page);
        }
        //3、将优惠券的id组成set集合
        Set<Long> ids = records.stream().map(UserCoupon::getCouponId).collect(Collectors.toSet());
        //4、从coupon优惠券表中查找优惠券信息
        List<Coupon> coupons = couponMapper.selectBatchIds(ids);
        List<CouponVO> couponVOS = BeanUtils.copyToList(coupons, CouponVO.class);
        // 4、返回分页数据
        return PageDTO.of(page, couponVOS);
    }

    /**
     * 保存用户优惠券
     * 两个操作：更新已经发放的数量，保存用户优惠券
     * @param msg
     */
    @Override
    @Transactional
    public void saveAndInrUserCoupon(CouponMsgDto msg) {
        //前面加了锁 能进入这里 一个优惠券只能有一个线程
        //需要做的操作有：更新coupon的已发放数量，在user_coupon表中插入一条记录
        //1、做确认操作  MySQL中确实有这个优惠券
        Coupon coupon = couponMapper.selectById(msg.getCouponId());
        if (coupon == null){
            throw new BadRequestException("优惠券不存在");
        }
        //2、优惠券存在 进行更新操作
        boolean isSuccess = couponMapper.updateCouponIssueById(msg.getCouponId());
        if (!isSuccess){
            throw new BadRequestException("优惠券已发放完");
        }
        //3、保存到user_coupon表中
        saveUserCoupon(coupon,msg.getUserId());

        //4、兑换码
        if (msg.getSerialNum()!= null) {
            exchangeCodeService.lambdaUpdate()
                    .set(ExchangeCode::getUserId, msg.getUserId())
                    .set(ExchangeCode::getStatus, ExchangeCodeStatus.USED)
                    .eq(ExchangeCode::getId, msg.getSerialNum())
                    .update();
        }
    }

    /**
     * 查询可用优惠券 给出可行的优惠方案
     *  用券相同时，保留优惠金额最高的方案
     *  金额相同时，保留用券最少的方案
     * @param orderCourses
     * @return
     */
    @Override
    public List<CouponDiscountDTO> findDiscountSolution(List<OrderCourseDTO> orderCourses) {
        //1、参数判断
        if (orderCourses==null){
            throw new BizIllegalException("参数不能为空");
        }
        //2、查询当前用户当前可用的优惠券
        List<Coupon> coupons = userCouponMapper.queryMyCoupons(UserContext.getUser());
        if (CollUtils.isEmpty(coupons)) {
            return CollUtils.emptyList();
        }

        //3、初筛，课程总价格不满优惠券可使用的最低价格那么去除
        int totalAmount = orderCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
        List<Coupon> firstAvailableCoupons = coupons.stream().filter(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).canUse(totalAmount, coupon)).collect(Collectors.toList());
        if(CollUtils.isEmpty(firstAvailableCoupons)){
            return CollUtils.emptyList();
        }


        //4、细筛，查询每一个优惠券对应的可用课程
        Map<Coupon,List<OrderCourseDTO>> secondAvailableCouponsMap =carefulChoose(firstAvailableCoupons,orderCourses);
        //5、排列出所有的可用方案  由于优惠券的使用顺序不同，可能导致最终的优惠金额不同。
        //我们要找出优惠金额最高的优惠券组合，就必须先找出所有的排列组合，然后分别计算出优惠金额，然后对比并找出最优解
        //全排列可以基于回溯算法
        // 排列组合
        ArrayList<Coupon> finalCoupons = new ArrayList<>(secondAvailableCouponsMap.keySet());
        List<List<Coupon>> solutions = PermuteMyUtil.permute(finalCoupons);
        // 添加单券的方案
        for (Coupon c : finalCoupons) {
            solutions.add(List.of(c));
        }
        //6、单线程：计算方案的优惠明细
        /***
         * 优惠明细分为单张优惠和叠券优惠
         *  1.单张优惠券：- 1）判断优惠券限定范围，找出范围内的课程
         *               - 2）计算课程总价
         *               - 3）判断券是否可用
         *               - 4）计算优惠金额
         *  2.叠加优惠券： - 1）判断优惠券限定范围，找出范围内的课程
         *                - 2）计算课程总价
         *                - 3）判断券是否可用
         *                - 4）计算优惠金额
         *                - 5）计算优惠明细
         */
//        List<CouponDiscountDTO> result = new ArrayList<>();
//        for (List<Coupon> solution : solutions) {
//            CouponDiscountDTO discountDetail = calculateDiscountDetail(secondAvailableCouponsMap, orderCourses,solution);
//            log.debug("优惠方案：{}，优惠金额：{}，规则{}", discountDetail.getIds(), discountDetail.getDiscountAmount(),discountDetail.getRules());
//            result.add(discountDetail);
//        }
        //6、多线程并行计算优惠方案
        List<CouponDiscountDTO> result =  multiThreadedParallelComputing(solutions,secondAvailableCouponsMap,orderCourses);
        //7、筛选最优解
        return findBaseChanges(result);
    }

    /**
     * 根据优惠券方案计算订单优惠明细
     * @return 优惠券优惠明细
     */
    @Override
    public CouponDiscountDTO getOrderCouponDetail(OrderCouponDTO dto) {
        //1、健壮性判断
        if (dto==null){
            throw new BizIllegalException("参数不能为空");
        }
        List<Long> userCouponIds = dto.getUserCouponIds();
        List<Coupon> coupons = userCouponMapper.queryCouponByUserCouponIds(userCouponIds,UserContext.getUser());
        if (CollUtils.isEmpty(coupons)){
            return null;//没有使用优惠券
        }
        List<OrderCourseDTO> courseList = dto.getCourseList();
        //2、计算 优惠券可以使用的范围
        Map<Coupon, List<OrderCourseDTO>> couponListMap = carefulChoose(coupons, courseList);
        return calculateDiscountDetail(couponListMap,courseList,coupons);
    }

    @Override
    @Transactional
    public void writeOffCoupon(List<Long> couponIds) {
        //1、检查list
        if(CollUtils.isEmpty(couponIds)){
            log.info("优惠券id为空");
            return;
        }
        //2、检查用户优惠券是否已经使用或者过期
        List<UserCoupon> userCoupons = lambdaQuery().eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        if (CollUtils.isEmpty(userCoupons)){
            log.info("优惠券列表为空");
            return;
        }
        LocalDateTime usedTime = LocalDateTime.now();
        List<UserCoupon> list  = userCoupons.stream().filter(userCoupon -> userCoupon.getStatus() == UserCouponStatus.UNUSED
                        && !userCoupon.getTermEndTime().isBefore(usedTime)
                        && !userCoupon.getTermBeginTime().isAfter(usedTime))
                .map(userCoupon -> {
                    UserCoupon newUerCoupon = new UserCoupon();
                    newUerCoupon.setId(userCoupon.getId());
                    newUerCoupon.setCouponId(userCoupon.getCouponId());
                    newUerCoupon.setStatus(UserCouponStatus.USED);
                    newUerCoupon.setUsedTime(usedTime);
                    return newUerCoupon;
                })
                .collect(Collectors.toList());
        //3、更新用户优惠券表中优惠券状态
        boolean isSuccess = updateBatchById(list);
        if(!isSuccess){
            throw new BizIllegalException("优惠券核销失败");
        }
        //4、更新优惠券表中优惠券使用的数量
        List<Long> usedCouponIds = list.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        int i =  couponMapper.incrUsedNums(usedCouponIds,1);
        if(i<1){
            throw new BizIllegalException("优惠券核销失败");
        }
    }

    /**
     * 退还优惠券
     * @param couponIds
     */
    @Override
    @Transactional
    public void refundCoupon(List<Long> couponIds) {
        //1、参数检测
        List<UserCoupon> userCoupons = lambdaQuery().eq(UserCoupon::getUserId, UserContext.getUser())
                .in(UserCoupon::getCouponId, couponIds)
                .list();
        if (CollUtils.isEmpty(userCoupons)){
            log.info("优惠券id为空");
            return;
        }
        //2、检验用户券是否处于已经使用的状态 不是不需要处理
        List<UserCoupon> needRefundCoupons = userCoupons.stream()
                .filter(userCoupon -> userCoupon.getStatus() == UserCouponStatus.USED)
                .map(userCoupon -> {
                    LocalDateTime now = LocalDateTime.now();
                    UserCoupon newUerCoupon = new UserCoupon();
                    newUerCoupon.setId(userCoupon.getId());
                    newUerCoupon.setStatus(now.isAfter(userCoupon.getTermEndTime()) ?
                            UserCouponStatus.EXPIRED : UserCouponStatus.UNUSED);
                    return newUerCoupon;
                })
                .collect(Collectors.toList());
        if (CollUtils.isEmpty(needRefundCoupons)){
            log.info("优惠券列表为空");
            return;
        }
        boolean b = updateBatchById(needRefundCoupons);
        if (!b){
            throw new BizIllegalException("优惠券退还失败");
        }
        //3、优惠券表中used_nums-1
        List<Long> ids = userCoupons.stream().map(UserCoupon::getCouponId).collect(Collectors.toList());
        int i = couponMapper.incrUsedNums(ids, -1);
        if (i<1){
            throw new BizIllegalException("优惠券退还失败");
        }
    }

    /**
     * 查询优惠券规则
     * @param couponIds
     * @return
     */
    @Override
    public List<String> queryCouponRules(List<Long> couponIds) {
        if (CollUtils.isEmpty(couponIds)){
            return null;
        }
        List<Coupon> coupons = couponMapper.queryByCouponIds(couponIds);
        return coupons.stream().map(coupon -> DiscountStrategy.getDiscount(coupon.getDiscountType()).getRule(coupon))
                .collect(Collectors.toList());
    }

    /**
     * 查找最优的组合
     * @param result
     * @return
     */
    private List<CouponDiscountDTO> findBaseChanges(List<CouponDiscountDTO> result) {
        //声明两个Map
        //  一个记录 用的券相同时 优惠的金额
        //  一个记录 优惠金额相同时 用的券
        Map<String,CouponDiscountDTO> discountAmountMap = new HashMap<>();
        Map<Integer,CouponDiscountDTO> amountMap = new HashMap<>();
        //遍历我们所有的优惠方案
        for (CouponDiscountDTO couponDiscountDTO : result) {
            //获取当前方案所使用的优惠券 从小到大排列 逗号分割
            String ids  = couponDiscountDTO.getIds().stream().sorted(Long::compare).map(String::valueOf).collect(Collectors.joining(","));
            CouponDiscountDTO old = discountAmountMap.get(ids);
            if (old!=null && old.getDiscountAmount()>=couponDiscountDTO.getDiscountAmount())continue;
            old = amountMap.get(couponDiscountDTO.getDiscountAmount());
            if (old!=null && old.getIds().size()<=couponDiscountDTO.getIds().size())continue;
            //需要进行修改 或者添加
            discountAmountMap.put(ids,couponDiscountDTO);
            amountMap.put(couponDiscountDTO.getDiscountAmount(),couponDiscountDTO);
        }
        //对两者进行取交集
        Collection<CouponDiscountDTO> bestRes = CollUtils.intersection(discountAmountMap.values(), amountMap.values());
        return bestRes.stream().sorted(Comparator.comparing(CouponDiscountDTO::getDiscountAmount).reversed()).collect(Collectors.toList());
    }

    /***
     * 多线程并行计算优惠方案
     * @param solutions
     * @param secondAvailableCouponsMap
     * @param orderCourses
     * @return
     */
    private List<CouponDiscountDTO> multiThreadedParallelComputing(List<List<Coupon>> solutions, Map<Coupon, List<OrderCourseDTO>> secondAvailableCouponsMap, List<OrderCourseDTO> orderCourses) {
        List<CouponDiscountDTO> res = Collections.synchronizedList(new ArrayList<>(solutions.size()));
        CountDownLatch countDownLatch = new CountDownLatch(solutions.size());
        for (List<Coupon> solution : solutions) {
            CompletableFuture.supplyAsync(() -> {
                CouponDiscountDTO couponDiscountDTO = calculateDiscountDetail(secondAvailableCouponsMap, orderCourses, solution);
                return couponDiscountDTO;
            },calculateOfferThreadPool).thenAccept(couponDiscountDTO -> {
                res.add(couponDiscountDTO);
                countDownLatch.countDown();
            });
        }
        try {
            countDownLatch.await(3,TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            log.error("优惠方案计算被中断，{}", e.getMessage());
        }
        return res;
    }

    /**
     * 计算方案对应的优惠明细
     * @param secondAvailableCouponsMap coupon对应可用课程的map
     * @param orderCourses 订单中所有的课程
     * @param solution 方案
     * @return
     */
    private CouponDiscountDTO calculateDiscountDetail(Map<Coupon, List<OrderCourseDTO>> secondAvailableCouponsMap, List<OrderCourseDTO> orderCourses, List<Coupon> solution) {
        //1、创建返回结果
        CouponDiscountDTO discountDetail = new CouponDiscountDTO();
        //2、建立商品id和优惠金额的映射关系 初始为0
        Map<Long, Integer> id2Amount = orderCourses.stream().collect(Collectors.toMap(OrderCourseDTO::getId, orderCourseDTO -> 0));
        //3、遍历方案 计算折扣明细
        for (Coupon coupon : solution) {
            //3.1 根据优惠券id取出可用的课程
            List<OrderCourseDTO> availableCourses = secondAvailableCouponsMap.get(coupon);
            //3.2 计算课程总价
            int totalPrice = availableCourses.stream().mapToInt(value->value.getPrice()-id2Amount.get(value.getId())).sum();
            //3.3 判断优惠券是否可用
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (!discount.canUse(totalPrice, coupon)){//
               continue;
            }
            //3.5 优惠券可用，计算优惠金额
            int amountOfDiscount = discount.calculateDiscount(totalPrice, coupon);
            //3.6 更新商品折扣明细
            updateDiscountDetail(id2Amount,availableCourses,totalPrice,amountOfDiscount);
            //3.7 在结果中累加优惠金额
            discountDetail.setDiscountAmount(discountDetail.getDiscountAmount()+amountOfDiscount);
            //3.8 在结果中加入使用优惠券id的数组
            discountDetail.getIds().add(coupon.getId());
            discountDetail.getRules().add(discount.getRule(coupon));
        }
        discountDetail.setDiscountDetail(id2Amount);
        return discountDetail;
    }

    /**
     * 更新商品id和优惠金额的映射关系的函数
     * @param id2Amount
     * @param availableCourses
     * @param totalPrice
     * @param amountOfDiscount
     */

    private void updateDiscountDetail(Map<Long, Integer> id2Amount, List<OrderCourseDTO> availableCourses, int totalPrice, int amountOfDiscount) {
        //1、遍历可用课程，只需要遍历到倒数第二位就好
        int remainAmount = amountOfDiscount;
        int size = availableCourses.size();
        for (int i = 0; i < size - 1; i++) {
            OrderCourseDTO orderCourseDTO = availableCourses.get(i);
            //2、计算当前课程的优惠金额
            int discountAmount = amountOfDiscount *  orderCourseDTO.getPrice()  / totalPrice;
            remainAmount-=discountAmount;
            //3、更新商品id和优惠金额的映射关系
            id2Amount.put(orderCourseDTO.getId(), id2Amount.get(orderCourseDTO.getId())+discountAmount);
        }
        //4、处理最后一个
        id2Amount.put(availableCourses.get(size -1).getId(),id2Amount.get(availableCourses.get(size -1).getId())+remainAmount);
    }


    /***
     * 仔细的筛选
     * @param firstAvailableCoupons
     * @param orderCourses
     * @return
     * 首先要基于优惠券的限定范围对课程筛选，找出可用课程。如果没有可用课程，则优惠券不可用。
     * 然后对可用课程计算总价，判断是否达到优惠门槛，没有达到门槛则优惠券不可用
     */
    private Map<Coupon, List<OrderCourseDTO>> carefulChoose(List<Coupon> firstAvailableCoupons, List<OrderCourseDTO> orderCourses) {

        Map<Coupon, List<OrderCourseDTO>> secondAvailableCouponsMap = new HashMap<>(firstAvailableCoupons.size());
        //1、遍历初筛的优惠券列表
        for (Coupon coupon : firstAvailableCoupons) {
            List<OrderCourseDTO> availableCourses = null;
            //2、判断优惠券是否限定了范围
            if (coupon.getSpecific()){
                //2.1 查询限定范围优惠券的课程
                List<CouponScope> list = couponScopeService.lambdaQuery().eq(CouponScope::getCouponId, coupon.getId()).list();
                //2.2 获取范围对应的分类id
                Set<Long> scopeIds = list.stream().map(CouponScope::getBizId).collect(Collectors.toSet());
                //2.3 筛选课程
                availableCourses = orderCourses.stream()
                        .filter(orderCourse -> scopeIds.contains(orderCourse.getCateId()))
                        .collect(Collectors.toList());
            }else {
                //没有限定范围 说明该优惠券可以用于所有的课程 （初筛的时候已经计算过课程的总价钱是肯定大于门槛的 否则不会在orderCourses）
                secondAvailableCouponsMap.put(coupon, orderCourses);
            }
            //2.4 健壮性判断
            if (CollUtils.isEmpty(availableCourses)){
                continue;
            }
            //3、计算当前优惠券可以用于课程的总价钱，判断是否达到优惠券使用的最低门槛
            int sumPrice = availableCourses.stream().mapToInt(OrderCourseDTO::getPrice).sum();
            Discount discount = DiscountStrategy.getDiscount(coupon.getDiscountType());
            if (discount.canUse(sumPrice, coupon)){
                log.info("优惠券：{} 可以用券的课程：{}", coupon.getName(), availableCourses);
                secondAvailableCouponsMap.put(coupon, availableCourses);
            }
        }
        return secondAvailableCouponsMap;
    }










    private void saveUserCoupon(Coupon coupon, Long userId) {
        UserCoupon userCoupon = new UserCoupon();
        // 1.基本信息
        userCoupon.setUserId(userId);
        userCoupon.setCouponId(coupon.getId());
        // 2.有效期信息
        if (coupon.getTermDays() == null || coupon.getTermDays() == 0) {
            userCoupon.setTermBeginTime(coupon.getTermBeginTime());
            userCoupon.setTermEndTime(coupon.getTermEndTime());
        } else {
            userCoupon.setTermBeginTime(LocalDateTime.now());
            userCoupon.setTermEndTime(LocalDateTime.now().plusDays(coupon.getTermDays()));
        }
        save(userCoupon);
    }
}

